#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>

#define NUM 1024
#define SIZE 64
#define SEP " "

#define NoneRedir   0
//redir输出重定向
#define OutputRedir 1
//追加重定向
#define AppendRedir 2
//输入重定向
#define InputRedir  3

int redir = NoneRedir;

char cwd[1024];
char enval[1024];//for test存放任意一个环境变量的字符数组
int lastcode = 0;// 退出码
char* filename = NULL;

char* homepath()
{
    char* home = getenv("HOME");
    if(home) return home;
    else return (char*)".";
}
const char *getUsername()
{
    const char* name = getenv("USER");
    if(name) return name;
    else return "none";
}

const char *getHostname()
{
    const char* hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return "none";
}
const char *getCwd()
{
    const char* cwd = getenv("PWD");
    if(cwd) return cwd;
    else return "none";
}
int getUserCommand(char* command,int num)
{
    printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());
    // 获取命令行输入
    char*r =  fgets(command,num,stdin);
    if(r == NULL) return -1;
    command[strlen(command) - 1] = '\0';// 清除'\n'字符，由于必定输入'\n'，因此不会有越界的问题
    return strlen(command);
}
void commandSplit(char *in, char *out[])
{
    int argc = 0;
    out[argc++] = strtok(in,SEP);
    while(out[argc++] = strtok(NULL,SEP));
}
int execute(char* argv[])
{
    pid_t id = fork();
    if(id < 0) return -1;
    else if(id == 0)//子进程
    {
        int fd = 0;
        if(redir == InputRedir)
        {
            fd = open(filename, O_RDONLY);
            dup2(fd,0);
        }
        else if(redir == OutputRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
            dup2(fd,1);
        }
        else if(redir == AppendRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
            dup2(fd,1);
        }else{}
        execvp(argv[0],argv);
        exit(1);
    }
    else
    {
       int status = 0;
       pid_t rid = waitpid(id,&status,0);
       if(rid > 0){
           lastcode = WEXITSTATUS(status);//获取退出码
       }
    }
    return 0;
}

void cd(const char* path)
{
    chdir(path);
    char tmp[1024];
    getcwd(tmp,sizeof(tmp));//注意是库函数getcwd，不是上面写的那个
    //环境变量不能在临时变量中储存，要在一个持久的缓冲区内，所以存在全局变量cwd中
    sprintf(cwd,"PWD=%s",tmp);
    putenv(cwd);
}
//如果是内建命令，执行内建命令
int doBuildin(char* argv[])
{
    if(strcmp(argv[0],"cd")==0)
    {
        char* path = NULL;
        if(argv[1] == NULL) path = homepath();
        else path = argv[1];
        cd(path);
        return 1;//1代表是内建命令
    }
    else if(strcmp(argv[0],"export")==0)
    {
        if(argv[1] == NULL) return 1;
        strcpy(enval,argv[1]);
        putenv(enval);
        return 1;
    }
    else if(strcmp(argv[0],"echo")==0)
    {
        if(argv[1] == NULL) 
        {
            printf("\n");
            return 1;
        }
        if(*(argv[1]) == '$' && strlen(argv[1]) > 1)
        {
           //这里只模拟打印退出码和环境变量
           char* val = argv[1]+1;// +1是为了跳过'$'符号
           if(strcmp(val,"?") == 0)
           {
               printf("%d\n",lastcode);//退出码
               lastcode = 0;
           }
           else
           {
               const char* enval = getenv(val);
               if(enval) printf("%s\n",getenv(val));
               else printf("\n");
           }   
           return 1;
        }
        else{
            printf("%s\n",argv[1]);
            return 1;
        }
    }
    //其他命令
    return 0;//代表不是内建命令
}

#define SkipSpace(pos) do{while(isspace(*pos)) pos++; }while(0)
void checkRedir(char usercommand[],int len)
{
    char *end = usercommand + len - 1;
    char* start = usercommand;
    while(end > start)
    {
        if(*end == '>')
        {
            if(*(end - 1)=='>')
            {
                //追加重定向
                *(end - 1) = '\0';//把重定向符号左右分开
                filename = end + 1;
                SkipSpace(filename);//取后面的命令，重定向符号右边有空格就跳过
                redir = AppendRedir;
                break;
            }else
            {
                //输出重定向
                *end = '\0';
                filename = end + 1;
                SkipSpace(filename);
                redir = OutputRedir;
                break;
            }
        }
        else if(*end == '<')
        {
            //输入重定向
            *end = '\0';
            filename = end + 1;
            SkipSpace(filename);//如果有空格就跳过
            redir = InputRedir;
            break;
        }
        else{
            --end;
        }
    }
}
int main()
{
    while(1)
    {
        redir = NoneRedir;
        filename = NULL;
         char usercommand[NUM];
         char* argv[SIZE];
         //获取命令行字符串
         int n = getUserCommand(usercommand,sizeof usercommand);
         if(n <= 0) continue;//空串
         //判断是否有重定向
         checkRedir(usercommand,strlen(usercommand));
         //分割字符串 "ls -a -l"->"ls" "-a" "-l"
         commandSplit(usercommand,argv);    
         //检查命令是否是内键命令
         n =  doBuildin(argv);
         if(n == 1) continue;
         //执行对应的命令
         execute(argv);
         
    }
    return 0;
}
